package com.wyw.redislock.learning.common.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Title   基于Redis   Setnx实现分布式锁
 * @Description
 * set resource_name my_random_value NX PX 30000
 * my_random_value：随机值，每个线程的随机值都不同，用于释放锁的时候的校验
 * NX：key不存在时设置成功，key存在时则设置不成功
 * PX：自动失效时间，出现异常情况，锁可以过期失效
 *
 * 实现原理：
 * 利用NX的原子性，多个线程并发时，只有一个线程可以设置成功
 * 设置成功即成功获得锁，可以执行后续的业务流程
 * 如果出现异常，过了锁的有效期，锁自动释放
 * 释放锁使用LUA脚本
 *
 * 注意事项：
 * 释放锁之前需要检验之前设置的随机数，防止释放错误，只有相同才能释放（如果在线程A执行结束前，A因为时间到期，自动释放，若是不加验证，会出现释放线程B的锁的情况）
 *
 * @Author Mr Wu yewen.wu.china@gmail.com
 * @Date 2024/3/29 9:45
 * Update History:
 * Author        Time            Content
 */
@Slf4j
public class RedisLock implements AutoCloseable {

    private RedisTemplate redisTemplate;
    //redis key
    private String key;
    //redis value
    private String value;
    //过期时间
    private int expireTime;

    public RedisLock(RedisTemplate redisTemplate, String key, int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.value = UUID.randomUUID().toString();//value唯一标识符，这样在释放锁时一定是释放自己的，防止因为锁已自动释放，引起的错误情况
        this.expireTime = expireTime;
    }

    /**
     * 获取锁
     * @author: Mr Wu    yewen.wu.china@gmail.com
     * @date: 2020/7/14 23:21
     * @return
     */
    public boolean getLock(){
        return redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        /*RedisCallback<Boolean> redisCallback = redisConnection -> {
            //设置NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();//如果key不存在，则设置成功；如果key存在，则设置不成功
            //设置过期时间
            Expiration expiration = Expiration.seconds(expireTime);
            //序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            //序列化value
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            //执行setNX操作
            return redisConnection.set(redisKey, redisValue, expiration, setOption);
        };
        //获取分布式锁
        return (boolean) redisTemplate.execute(redisCallback);*/
    }

    /**
     * 释放锁
     * @author: Mr Wu    yewen.wu.china@gmail.com
     * @date: 2020/7/14 23:33
     * @return
     */
    public boolean unLock(){
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        List<String> keys = Arrays.asList(key);
        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果："+result);
        return result;
    }


    //JDK1.7之后，自动关闭，这样就可以不再finally方法直接关闭
    @Override
    public void close() throws Exception {
        unLock();
    }
}

